package cagt.tools.probability;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

/**
 * Iterator dla federacji resetowalnych iterator�w. Federacja rozumiana jest
 * jako lista iterator�w s�u��cy do przegl�dania tego samegu typu element�w.
 * Iterator ten przegl�da wszystkie mo�liwe kombinacje element�w pochodz�cych z
 * zawartych w nim iterator�w.
 * 
 * @author Konrad Sza�kowski
 * 
 * @param <T>
 *            Typ elementu zwracanego przez iteratory z federacji.
 */
public class FederatedResetIterators<T> implements ResetIterator<List<T>> {

	private List<ResetIterator<T>> iterators;
	private List<T> currentResults;

	/**
	 * Standardowy konstruktor wymagaj�cy podania tablicy iterator�w. Tablica
	 * nie mo�e by� nullem lub by� pusta, r�wnie� wszystkie iteratory w niej
	 * musz� po resecie m�c si� porusz��.
	 * 
	 * @param its
	 *            Tablica iterator�w.
	 */
	public FederatedResetIterators(List<? extends ResetIterator<T>> its) {
		// Warunki pocz�tkowe
		if (its == null) {
			throw new NullPointerException();
		}
		if (its.size() == 0) {
			throw new IllegalArgumentException("Iterator list is empty.");
		}
		// Ustaw iterator - kopia tablicy
		iterators = new ArrayList<ResetIterator<T>>(its);
		currentResults = new ArrayList<T>();
		// Resetuj
		reset();
		// Sprawd� czy wszystkie iterator mog� si� porusza� po resecie
		boolean allCanMove = true;
		for (ResetIterator<T> iterator : iterators) {
			allCanMove = allCanMove && iterator.hasNext();
		}
		if (!allCanMove) {
			throw new IllegalArgumentException("Iterators cannot be stalled.");
		}
	}

	public void reset() {
		for (ResetIterator<T> iterator : iterators) {
			iterator.reset();
		}
		currentResults.clear();
	}

	public boolean hasNext() {
		boolean canMove = false;
		for (ResetIterator<T> iterator : iterators) {
			canMove = canMove || iterator.hasNext();
		}
		return canMove;
	}

	public List<T> next() {
		ListIterator<ResetIterator<T>> iteratorOfIterators = iterators
				.listIterator();
		while (iteratorOfIterators.hasNext()) {
			// Pobierz obecny iterator
			ResetIterator<T> iterator = iteratorOfIterators.next();
			if (iterator.hasNext()) {
				// Stan pocz�tkowy
				if (currentResults.size() < iterators.size()) {
					// Dodawaj do zape�nienia
					currentResults.add(iterator.next());
				} else {
					// Zast�p wynik na bie��cej pozycji
					currentResults.set(iteratorOfIterators.previousIndex(),
							iterator.next());
					// Wycofaj si� poza obecny
					iteratorOfIterators.previous();
					// Zresetuj wszystkie poprzednie wyniki
					while (iteratorOfIterators.hasPrevious()) {
						ResetIterator<T> previous = iteratorOfIterators
								.previous();
						previous.reset();
						currentResults.set(iteratorOfIterators.nextIndex(),
								previous.next());
					}
					break;
				}
			}
		}
		// Przerwij
		return new ArrayList<T>(currentResults);
	}

}
